package com.zy.cat.common.aspect;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import com.zy.cat.common.annotation.RedisCaching;
import com.zy.cat.common.annotation.RedisEvicts;
import com.zy.cat.common.annotation.RedisPut;
import com.zy.cat.common.annotation.RedisSingle;
import com.zy.cat.common.annotation.RedisEvicts.RedisEvict;

import lombok.extern.slf4j.Slf4j;

/**
 * AOP拦截 RedisCachingt、RedisEvict、RedisPut注解
 * 
 * @author Link
 * @date 2019-03-18 18:48
 */
@Aspect
@Component
@Slf4j
public class RedisCachingAspect {
	@Autowired
	@Qualifier("redis0")
	RedisTemplate<String, Object> redis0;
	@Autowired
	@Qualifier("redis1")
	RedisTemplate<String, Object> redis1;
	@Autowired
	@Qualifier("redis2")
	RedisTemplate<String, Object> redis2;
	@Autowired
	@Qualifier("redis3")
	RedisTemplate<String, Object> redis3;
	@Autowired
	@Qualifier("redis4")
	RedisTemplate<String, Object> redis4;
	@Autowired
	@Qualifier("redis5")
	RedisTemplate<String, Object> redis5;
	@Autowired
	@Qualifier("redis6")
	RedisTemplate<String, Object> redis6;
	@Autowired
	@Qualifier("redis7")
	RedisTemplate<String, Object> redis7;
	@Autowired
	@Qualifier("redis8")
	RedisTemplate<String, Object> redis8;
	@Autowired
	@Qualifier("redis9")
	RedisTemplate<String, Object> redis9;
	@Autowired
	@Qualifier("redis10")
	RedisTemplate<String, Object> redis10;
	@Autowired
	@Qualifier("redis11")
	RedisTemplate<String, Object> redis11;
	@Autowired
	@Qualifier("redis12")
	RedisTemplate<String, Object> redis12;
	@Autowired
	@Qualifier("redis13")
	RedisTemplate<String, Object> redis13;
	@Autowired
	@Qualifier("redis14")
	RedisTemplate<String, Object> redis14;
	@Autowired
	@Qualifier("redis15")
	RedisTemplate<String, Object> redis15;
	
	@Autowired
	@Qualifier("redis16")
	RedisTemplate<String, Object> redis16;
	@Autowired
	@Qualifier("redis17")
	RedisTemplate<String, Object> redis17;
	@Autowired
	@Qualifier("redis18")
	RedisTemplate<String, Object> redis18;
	@Autowired
	@Qualifier("redis19")
	RedisTemplate<String, Object> redis19;
	@Autowired
	@Qualifier("redis20")
	RedisTemplate<String, Object> redis20;

	@Around("@annotation(redisCaching)")
	public Object redisCaching(ProceedingJoinPoint joinPoint, RedisCaching redisCaching) {
		/** redis缓存操作信息 **/
		RedisPut put = redisCaching.put();
		/** redis删除操作信息 **/
		RedisEvict[] evicts = redisCaching.evict();

		Object obj = null;
		if (put != null && StringUtils.isNotBlank(put.key())) {
			String expectedTime = (System.currentTimeMillis() + put.lockExpire()) + "";
			String key = "";
			String hashKey = "";
			String[] keys = put.key().split("[+]");

			for (int i = 0; i < keys.length; i++) {
				key += parseKey(joinPoint, keys[i]);
			}
			String[] hashkeys;
			if (put.rtype() == 2) {
				hashkeys = put.hashKey().split("[+]");
				for (int i = 0; i < hashkeys.length; i++) {
					hashKey += parseKey(joinPoint, hashkeys[i]);
				}
			}

			String lockKey = key + "_" + hashKey + "_" + put.db();
			obj = getByRedis(key, hashKey, put.db(), put.rtype());
			if (obj == null) {
				obj = getByDB(key, hashKey, lockKey, put.db(), put.rtype(), put.timeUnit(), put.timeOut(), expectedTime,
						put.waitExpire(), joinPoint, put.lockExpire());
			}
		} else {
			try {
				obj = joinPoint.proceed();
			} catch (Throwable e) {
				log.error("redisCaching发生异常--》" + e.getLocalizedMessage());
				return null;
			}
		}

		// 有删除操作
		if (evicts != null && evicts.length > 0) {
			for (RedisEvict redisEvict : evicts) {
				String expectedTime = (System.currentTimeMillis() + redisEvict.lockExpire()) + "";
				String key = "";
				String hashKey = "";
				String[] keys = redisEvict.key().split("[+]");
				for (int i = 0; i < keys.length; i++) {
					key += parseKey(joinPoint, keys[i]);
				}
				String[] hashkeys = redisEvict.hashKeys();
				List<String> hashkeyList = new ArrayList<String>();
				if (redisEvict.rtype() == 2) {
					for (int i = 0; i < hashkeys.length; i++) {
						if (hashkeys[i].contains("+")) {
							String[] hkeys = hashkeys[i].split("[+]");
							hashKey = "";
							for (int j = 0; j < hkeys.length; j++) {
								hashKey += parseKey(joinPoint, hkeys[j]);
							}
							hashkeyList.add(hashKey);
						}
					}
				}
				String lockKey = key + "_" + hashKey + "_" + redisEvict.db();

				deleteByDB(key, hashkeyList.toArray(), redisEvict.db(), redisEvict.rtype(), lockKey, expectedTime,
						redisEvict.lockExpire(), redisEvict.waitExpire());
			}

		}
		return obj;
	}

	@Before("@annotation(redisEvict)")
	public void redisEvict(JoinPoint point, RedisEvict redisEvict) {
		// 有删除操作
		if (redisEvict != null) {
			String expectedTime = (System.currentTimeMillis() + redisEvict.lockExpire()) + "";
			String key = "";
			String hashKey = "";
			String[] keys = redisEvict.key().split("[+]");
			for (int i = 0; i < keys.length; i++) {
				key += parseKeyByJoinPoint(point, keys[i]);
			}
			String[] hashkeys = redisEvict.hashKeys();
			List<String> hashkeyList = new ArrayList<String>();
			if (redisEvict.rtype() == 2) {
				for (int i = 0; i < hashkeys.length; i++) {
					if (hashkeys[i].contains("+")) {
						String[] hkeys = hashkeys[i].split("[+]");
						hashKey = "";
						for (int j = 0; j < hkeys.length; j++) {
							hashKey += parseKeyByJoinPoint(point, hkeys[j]);
						}
						hashkeyList.add(hashKey);
					}
				}
			}
			String lockKey = key + "_" + hashKey + "_" + redisEvict.db();
			deleteByDB(key, hashkeyList.toArray(), redisEvict.db(), redisEvict.rtype(), lockKey, expectedTime,
					redisEvict.lockExpire(), redisEvict.waitExpire());
		}
	}

	@Around("@annotation(redisEvicts)")
	public Object redisEvicts(ProceedingJoinPoint point, RedisEvicts redisEvicts) {
		Object object = null;
		if (redisEvicts != null) {

			/* 先执行方法体-后删除操作 */
			if (!redisEvicts.before()) {
				try {
					object = point.proceed();
				} catch (Throwable e) {
					log.error("redisEvicts-->方法体报错-->" + e.getLocalizedMessage());
				}
			}

			RedisEvict[] tredisEvict = redisEvicts.evict();

			for (RedisEvict redisEvict : tredisEvict) {
				// 有删除操作
				if (redisEvict != null) {
					String expectedTime = (System.currentTimeMillis() + redisEvict.lockExpire()) + "";
					String key = "";
					String hashKey = "";
					String[] keys = redisEvict.key().split("[+]");
					for (int i = 0; i < keys.length; i++) {
						key += parseKey(point, keys[i]);
					}
					String[] hashkeys = redisEvict.hashKeys();
					List<String> hashkeyList = new ArrayList<String>();
					if (redisEvict.rtype() == 2) {
						for (int i = 0; i < hashkeys.length; i++) {
							if (hashkeys[i].contains("+")) {
								String[] hkeys = hashkeys[i].split("[+]");
								hashKey = "";
								for (int j = 0; j < hkeys.length; j++) {
									hashKey += parseKey(point, hkeys[j]);
								}
								hashkeyList.add(hashKey);
							}
						}
					}
					String lockKey = key + "_" + hashKey + "_" + redisEvict.db();
					deleteByDB(key, hashkeyList.toArray(), redisEvict.db(), redisEvict.rtype(), lockKey, expectedTime,
							redisEvict.lockExpire(), redisEvict.waitExpire());
				}
			}
			/* 先执行删除操作方法体-后执行方法体 */
			if (redisEvicts.before()) {
				try {
					object = point.proceed();
				} catch (Throwable e) {
					log.error("redisEvicts-->方法体报错-->" + e.getLocalizedMessage());
				}
			}
		}
		return object;
	}

	@Around("@annotation(redisPut)")
	public Object redisPut(ProceedingJoinPoint joinPoint, RedisPut redisPut) {
		/** redis缓存操作信息 **/
		RedisPut put = redisPut;
		Object obj = null;
		if (put != null && StringUtils.isNotBlank(put.key())) {
			String expectedTime = (System.currentTimeMillis() + put.lockExpire()) + "";
			String key = "";
			String hashKey = "";
			String[] keys = put.key().split("[+]");

			for (int i = 0; i < keys.length; i++) {
				key += parseKey(joinPoint, keys[i]);
			}
			String[] hashkeys;
			if (put.rtype() == 2) {
				hashkeys = put.hashKey().split("[+]");
				for (int i = 0; i < hashkeys.length; i++) {
					hashKey += parseKey(joinPoint, hashkeys[i]);
				}
			}

			String lockKey = key + "_" + hashKey + "_" + put.db();
			obj = getByRedis(key, hashKey, put.db(), put.rtype());
			if (obj == null) {
				obj = getByDB(key, hashKey, lockKey, put.db(), put.rtype(), put.timeUnit(), put.timeOut(), expectedTime,
						put.waitExpire(), joinPoint, put.lockExpire());
			}
		} else {
			try {
				obj = joinPoint.proceed();
			} catch (Throwable e) {
				log.error("redisPut发生异常--》" + e.getLocalizedMessage());
				return null;
			}
		}
		return obj;
	}
	
	@Around("@annotation(redisSingle)")
	public Object redisSingle(ProceedingJoinPoint joinPoint, RedisSingle redisSingle) {
		/** redis缓存操作信息 **/
		RedisSingle put = redisSingle;
		Object obj = null;
		if (put != null && StringUtils.isNotBlank(put.key())) {
			String expectedTime = (System.currentTimeMillis() + put.lockExpire() * 1000) + "";
			String key = "";
			String[] keys = put.key().split("[+]");
			for (int i = 0; i < keys.length; i++) {
				key += parseKey(joinPoint, keys[i]);
			}
			String lockKey = key;
			obj = getRedisSingleLock(key, lockKey, expectedTime, put.waitExpire(), joinPoint, put.lockExpire());
		}
//		else {
//			try {
//				obj = joinPoint.proceed();
//			} catch (Throwable e) {
//				log.error("redisPut发生异常--》" + e.getLocalizedMessage());
//			}
//		}
		return obj;
	}

	/**
	 * 
	 * @info 方法体上锁操作
	 * @author Link
	 * @date 2020-03-19 11:57
	 * @param key
	 * @param hashKey
	 * @param lockKey
	 * @param db
	 * @param rtype
	 * @param unit
	 * @param timeout
	 * @param expectedTime
	 * @param waitExpire
	 * @param pjp
	 * @param lockExpire
	 * @return
	 */
	private Object getRedisSingleLock(String key, String lockKey, String expectedTime, long waitExpire,
			ProceedingJoinPoint pjp, long lockExpire) {
		Object result = null;
		if (lock(lockKey, expectedTime, lockExpire)) {
			log.info("获取锁--->{}-key:{}", lockKey, key);
			try {
				result = pjp.proceed();
			} catch (Throwable e) {
				log.error("Redis执行方法体出错--->{}-key:{}", e.getMessage(), key);
			} finally {
				log.info("释放锁--->{}-key:{}", lockKey, key);
				unlock(lockKey, expectedTime);
			}
		} else {
			log.info("等待锁--->{}-key:{}", lockKey, key);
			try {
				Thread.sleep(waitExpire);
			} catch (InterruptedException e) {
				log.error("Redis等待获取锁出错--->{}-key:{}", e.getMessage(), key);
			} finally {
				result = getRedisSingleLock(key, lockKey, expectedTime, waitExpire, pjp, lockExpire);
			}
		}
		return result;
	}

	/**
	 * 从数据库获取数据
	 * 
	 * @author Link
	 * @date 2019-06-27 10:21
	 * @param key
	 * @param hashKey
	 * @param lockKey
	 * @param db
	 * @param rtype
	 * @param unit
	 * @param timeout
	 * @param expectedTime
	 * @param waitExpire
	 * @param pjp
	 * @param lockExpire
	 * @return
	 */
	private Object getByDB(String key, String hashKey, String lockKey, int db, int rtype, TimeUnit unit, long timeout,
			String expectedTime, long waitExpire, ProceedingJoinPoint pjp, long lockExpire) {
		Object result = null;
		Object obj = getByRedis(key, hashKey, db, rtype);
		if (obj != null) {
			return obj;
		}
		if (lock(lockKey, expectedTime, lockExpire)) {
			try {
				result = pjp.proceed();
				if (result != null) {
					if (rtype == 1) {
						if(timeout>0) {
							selectDB(db).opsForValue().set(key, result, timeout, unit);
						}else {
							selectDB(db).opsForValue().set(key, result);
							selectDB(db).persist(key);
						}
						
					} else if (rtype == 2) {
						if (selectDB(db).hasKey(key)) {
							selectDB(db).opsForHash().put(key, hashKey, result);
						} else {
							selectDB(db).opsForHash().put(key, hashKey, result);
							if(timeout>0) {
								selectDB(db).expire(key, timeout, unit);
							}else {
								selectDB(db).persist(key);
							}
						}
					}
				}
				
//				result = pjp.proceed();
//				if (result != null) {
//					if (rtype == 1) {
//						selectDB(db).opsForValue().set(key, result, timeout, unit);
//					} else if (rtype == 2) {
//						if(selectDB(db).hasKey(key)) {
//							selectDB(db).opsForHash().put(key, hashKey, result);
//						}else {
//							selectDB(db).opsForHash().put(key, hashKey, result);
//							selectDB(db).expire(key, timeout, unit);
//						}
//					}
//				}
			} catch (Throwable e) {
				log.error("Redis执行方法体出错--->{}-key:{},DB:{}", e.getMessage(),key,db);
			} finally {
				unlock(lockKey, expectedTime);
			}
		} else {
			try {
				Thread.sleep(waitExpire);
			} catch (InterruptedException e) {
				log.error("Redis等待获取锁出错--->{}-key:{},DB:{}", e.getMessage(),key,db);
			} finally {
				result = getByDB(key, hashKey, lockKey, db, rtype, unit, timeout, expectedTime, waitExpire, pjp,
						lockExpire);
			}
		}
		return result;
	}

	/**
	 * 删除Redis 数据
	 * 
	 * @author Link
	 * @date 2019-03-19 11:02
	 * @param key
	 * @param hashKeys
	 * @param db
	 * @param rtype
	 * @param lockKey
	 * @param expectedTime
	 * @param lockExpire
	 * @param waitExpire
	 */
	private void deleteByDB(String key, Object[] hashKeys, int db, int rtype, String lockKey, String expectedTime,
			long lockExpire, long waitExpire) {
		if (lock(lockKey, expectedTime, lockExpire)) {
			try {
				if (rtype == 1) {
					selectDB(db).delete(key);
				} else if (rtype == 2) {
					selectDB(db).opsForHash().delete(key, hashKeys);
				}
			} catch (Throwable e) {
				log.error("Redis执行方法体出错--->{}-key:{},DB:{}", e.getMessage(),key,db);
			} finally {
				unlock(lockKey, expectedTime);
			}
		} else {
			try {
				Thread.sleep(waitExpire);
			} catch (InterruptedException e) {
				log.error("Redis等待获取锁出错--->{}-key:{},DB:{}", e.getMessage(),key,db);
			} finally {
				deleteByDB(key, hashKeys, db, rtype, lockKey, expectedTime, lockExpire, waitExpire);
			}
		}
	}

	/**
	 * 从redis 获取数据
	 * 
	 * @author Link
	 * @date 2019-03-19 09:36
	 * @param key
	 * @param hashKey
	 * @param db
	 * @param rtype
	 * @return
	 */
	private Object getByRedis(String key, String hashKey, int db, int rtype) {
		Object result = null;
		if (rtype == 1) {
			if (key != null) {
				result = selectDB(db).opsForValue().get(key);
			}
		} else if (rtype == 2) {
			if (key != null && hashKey != null) {
				result = selectDB(db).boundHashOps(key).get(hashKey);
			}

		}
		return result;
	}

	/**
	 * 
	 * @Title 获取缓存的key
	 * @Description key 定义在注解上，支持SPEL表达式
	 * @Company 掌赢科技-移动广告平台-技术部
	 * @Author Link
	 * @Date 2017年2月22日 下午2:49:03
	 */
	@SuppressWarnings("rawtypes")
	private String parseKey(ProceedingJoinPoint pjp, String key) {
		// 获取参数的类型
		Method method = null;
		Object[] args = pjp.getArgs();
		// 过滤不使用SpEL表达式的key
		if (!key.contains("#")) {
			return key;
		}
		key = key.replaceAll("\\s*", "");
		/*
		 * 获取被拦截方法对象,MethodSignature.getMethod() 获取的是顶层接口或者父类的方法对象,
		 * 而缓存的注解在实现类的方法上,所以应该使用反射获取当前对象的方法对象
		 */
		Class[] argTypes = new Class[pjp.getArgs().length];
		for (int i = 0; i < args.length; i++) {
			if(args[i]!=null) {
				if(args[i]!=null) {
					argTypes[i] = args[i].getClass();
				}
			}
		}
		try {
			// log.info("pjp.getSignature().getName()--->" + pjp.getSignature().getName());
			MethodSignature signature = (MethodSignature) pjp.getSignature();
			method = signature.getMethod();
		} catch (SecurityException e) {
			e.printStackTrace();
		}

		// 获取被拦截方法参数名列表(使用Spring支持类库)
		LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
		String[] paraNameArr = u.getParameterNames(method);

		// 使用SPEL进行key的解析
		ExpressionParser parser = new SpelExpressionParser();
		// SPEL上下文
		StandardEvaluationContext context = new StandardEvaluationContext();
		// 把方法参数放入SPEL上下文中
		for (int i = 0; i < paraNameArr.length; i++) {
			context.setVariable(paraNameArr[i], args[i]);
		}
		return parser.parseExpression(key).getValue(context, String.class);

	}

	/**
	 * 
	 * @Title 获取缓存的key
	 * @Description key 定义在注解上，支持SPEL表达式
	 * @Company 掌赢科技-移动广告平台-技术部
	 * @Author Link
	 * @Date 2017年2月22日 下午2:49:03
	 */
	@SuppressWarnings("rawtypes")
	private String parseKeyByJoinPoint(JoinPoint pjp, String key) {
		// 获取参数的类型
		Method method = null;
		Object[] args = pjp.getArgs();
		// 过滤不使用SpEL表达式的key
		if (!key.contains("#")) {
			return key;
		}
		key = key.replaceAll("\\s*", "");
		/*
		 * 获取被拦截方法对象,MethodSignature.getMethod() 获取的是顶层接口或者父类的方法对象,
		 * 而缓存的注解在实现类的方法上,所以应该使用反射获取当前对象的方法对象
		 */

		Class[] argTypes = new Class[pjp.getArgs().length];
		for (int i = 0; i < args.length; i++) {
			if(args[i]!=null) {
				argTypes[i] = args[i].getClass();
			}
		}
		try {
			// log.info("pjp.getSignature().getName()--->" + pjp.getSignature().getName());
			MethodSignature signature = (MethodSignature) pjp.getSignature();
			method = signature.getMethod();
		} catch (SecurityException e) {
			e.printStackTrace();
		}

		// 获取被拦截方法参数名列表(使用Spring支持类库)
		LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
		String[] paraNameArr = u.getParameterNames(method);

		// 使用SPEL进行key的解析
		ExpressionParser parser = new SpelExpressionParser();
		// SPEL上下文
		StandardEvaluationContext context = new StandardEvaluationContext();
		// 把方法参数放入SPEL上下文中
		for (int i = 0; i < paraNameArr.length; i++) {
			context.setVariable(paraNameArr[i], args[i]);
		}
		return parser.parseExpression(key).getValue(context, String.class);

	}

	/**
	 * 加锁 ,应用中当调用当前方法返回if(!lock)时建议抛出异常
	 * 
	 * @param key          key
	 * @param expectedTime 期望时间值 = 当前系统毫秒值 + 业务容忍超时时间
	 * @return 加锁是否成功
	 */
	private boolean lock(String lockkey, String expectedTime, long lockExpire) {
		// log.info(lockkey + "<------获取锁------->");

		/*
		 * setIfAbsent方法参考:http://redis.cn/commands/hsetnx.html 个人理解:
		 * 当key不存在时添加redis列值为value并返回true,如果存在则直接返回false
		 */
		// 当不存在key的一列时候设置value并返回true
		if (redis15.opsForValue().setIfAbsent(lockkey, expectedTime)) {
			// log.info(lockkey +
			// "<------获取锁成功------->"+redis15.opsForValue().get(lockkey));
			return true;
		}
		if (lockExpire > 0) {
			// 判断是否超时
			String oldTime = (String) redis15.opsForValue().get(lockkey);
			long currentTimeMillis = System.currentTimeMillis();
			if (!StringUtils.isEmpty(oldTime) && currentTimeMillis > Long.parseLong(oldTime)) {
				String oldValue = (String) redis15.opsForValue().getAndSet(lockkey, expectedTime); // 利用redis是单线程的特性
				if (!StringUtils.isEmpty(oldValue) && oldValue.equals(oldTime)) {
					// log.info(lockkey + "<------超时获取锁成功------->" + oldValue);
					return true;
				}
			}
		}

		return false;
	}

	/**
	 * 解锁 删除指定key的redis列
	 * 
	 * @param lockKey
	 * 
	 */
	private void unlock(String lockKey, String expectedTime) {
		// log.info(lockKey + "<------注销锁------->");
		String value = (String) redis15.opsForValue().get(lockKey);
		if (!StringUtils.isEmpty(value) && value.equals(expectedTime)) {
			redis15.opsForValue().getOperations().delete(lockKey);
		}
	}

	/**
	 * 
	 * @Title 选择操作的Redis DB
	 * @Description
	 * @Company 掌赢科技-移动广告平台-技术部
	 * @Author Link
	 * @Date 2017年2月22日 下午5:30:52
	 */
	private RedisTemplate<String, Object> selectDB(int db) {
		RedisTemplate<String, Object> result = null;
		switch (db) {
		case 1:
			result = redis1;
			break;
		case 2:
			result = redis2;
			break;
		case 3:
			result = redis3;
			break;
		case 4:
			result = redis4;
			break;
		case 5:
			result = redis5;
			break;
		case 6:
			result = redis6;
			break;
		case 7:
			result = redis7;
			break;
		case 8:
			result = redis8;
			break;
		case 9:
			result = redis9;
			break;
		case 10:
			result = redis10;
			break;

		case 11:
			result = redis11;
			break;
		case 12:
			result = redis12;
			break;
		case 13:
			result = redis13;
			break;
		case 14:
			result = redis14;
			break;
		case 15:
			result = redis15;
			break;
		case 16:
			result = redis16;
			break;
		case 17:
			result = redis17;
			break;
		case 18:
			result = redis18;
			break;
		case 19:
			result = redis19;
			break;
		case 20:
			result = redis20;
			break;
		default:
			result = redis0;
			break;
		}
		return result;
	}
}